#include "feature.h"
#include "dataset.h"
#include "stdio.h"
#include <iostream>
#include <fstream>
#include <iomanip>

using namespace cv;
using namespace std;

/*
parse_file:
Fills features matrix and responses matrix using samples from file
*/
void parse_file(const char* filename, cv::Mat& featuresMatrix, cv::Mat& responses)
{
	string line;
	ifstream myfile;
	int total_number, i=0, columns_number=3;
	myfile.open(filename, ios::in);

	getline(myfile, line);
	total_number = atoi(line.c_str());
	getline(myfile, line);  //Skip header

	featuresMatrix = Mat(total_number, 72, CV_32F);
	responses = Mat();

	while (getline(myfile, line))
	{
		stringstream strstr(line);
		string word = "";
		while (getline(strstr, word, ';'))
		{
			if (i % columns_number == 0) //Filename
			{
				Mat rowForFeature = featuresMatrix(Range(i/columns_number,i/columns_number + 1), Range::all()); 
				Mat image = imread(word.c_str());
				Mat gray, bin, hist;
				cvtColor(image, gray, COLOR_BGR2GRAY);
				//threshold(gray, bin, 150, 255, THRESH_BINARY_INV);
				bin = gray;
				calcRadAngleHist(bin, rowForFeature, 72);
				//featuresMatrix.push_back(rowForFeature);

				//showHist(rowForFeature);
				//cv::imshow("gray", gray);
				//waitKey(0);
			}
			else if (i % columns_number == 1) //Class name
			{

			}
			else if (i % columns_number == 2) //Class id
			{
				int classId = atoi(word.c_str());
				responses.push_back((float)classId);
			}
			i++;
		}
	}
	myfile.close();
}

void printPredictionStatistics(const char* resultFilename, cv::Mat trueValues, cv::Mat predictions, const char* suffix="")
{
	int nClasses = 153; //50
	Mat_<float> confusion = Mat_<float>::zeros(nClasses, nClasses);
	ofstream myfile(resultFilename);
	int n_fails = 0;
	int total_size = trueValues.rows;
	for (int i = 0; i < total_size; ++i) 
	{ 
		int trueValue = (int)(trueValues.at<float>(i)); 
		int predictedValue = (int)(predictions.at<float>(i)); 
		confusion(trueValue - 1, predictedValue - 1)++;
		if (predictedValue != trueValue)
		{
			myfile << i << ". Failed. Required " << trueValue << ", got " << predictedValue << endl;
			printf("%i. Failed. Required %i, got %i\n", i, trueValue, predictedValue);
			n_fails++;
		}
	}
	printf("Total number of fails %i out of %i\n", n_fails, trueValues.rows);

	myfile << "Total;Success;Fails;Success_percent\n";
	myfile << total_size << ";" << total_size - n_fails << ";" << n_fails << ";" << 100.0 * (1.0 - (double)(n_fails)/(double)total_size) << endl;
	myfile << confusion << endl;


	for(int i = 0; i < nClasses; i++)
		confusion.row(i) /= float(sum(confusion.row(i))[0]);

	cout << cv::trace(confusion) / nClasses << endl;

	cv::normalize(confusion, confusion, 0, 255, CV_MINMAX);
	cv::resize(confusion, confusion, cv::Size(), 8, 8, INTER_NEAREST);

	//cv::imshow("conf", confusion);
	//char k = cv::waitKey(0);

	int HOCS_A = 2, HOCS_B = 3, HOCS_NB = 21, HOCS_NSC = 25;

	string filename = "svm_hoc_linear_svc_confusion";
	filename +=  suffix;
	filename+= ".png";

	//cv::String filename = "svm_hoc_linear_svc_confusion.png";
	//filename = cv::format(filename.c_str(), HOCS_A, HOCS_B, HOCS_NB, HOCS_NSC);

	cv::imwrite(filename, confusion);
}

void testRForest (string train_xml_path, string test_data_path, string out_dir)
{
	int nFeatures = 72;
	cv::Ptr<CvRTrees> rtree = new CvRTrees;
	rtree->load(train_xml_path.c_str());
	
	{ //Test results
		cv::Mat test_set;
		cv::Mat trueResponses;
		cv::Mat predictions; 
		parseFileAndExtractFeatures(Path::combine(test_data_path, "test_50.csv").c_str(), test_set, trueResponses, nFeatures, calcRadAngleHist/*calcHoCS*/);

		float result;
		for (int i = 0; i < test_set.rows; ++i) 
		{ 
			result = rtree->predict(test_set.row(i), Mat());
			predictions.push_back(result);
		}

		printPredictionStatistics(Path::combine(out_dir, "random_forest_1000_trees_d20_hra_test_result.txt").c_str(), trueResponses, predictions);
	}
	{ //Train results
		cv::Mat test_set;
		cv::Mat trueResponses;
		cv::Mat predictions; 
		parseFileAndExtractFeatures(Path::combine(test_data_path, "train_50.csv").c_str(), test_set, trueResponses, nFeatures, calcRadAngleHist/*calcHoCS*/);

		float result;
		for (int i = 0; i < test_set.rows; ++i) 
		{ 
			result = rtree->predict(test_set.row(i), Mat()); 
			predictions.push_back(result);
		}

		printPredictionStatistics(Path::combine(out_dir, "random_forest_1000_trees_d20_hra_train_result.txt").c_str(), trueResponses, predictions);
	}
}

void testNaiveBayes (string train_xml_path, string test_data_path, string out_dir)
{
	int nFeatures = 72;
	CvNormalBayesClassifier bayes;
	bayes.load(train_xml_path.c_str());

	{ //Test results
		cv::Mat test_set;
		cv::Mat trueResponses;
		cv::Mat predictions; 
		parseFileAndExtractFeatures(Path::combine(test_data_path, "test.csv").c_str(), test_set, trueResponses, nFeatures, calcRadAngleHist);

		float result;
		result = bayes.predict(test_set, &predictions);	

		printPredictionStatistics(Path::combine(out_dir, "naive_bayes_test_result.txt").c_str(), trueResponses, predictions);
	}
	{ // Train results
		cv::Mat test_set;
		cv::Mat trueResponses;
		cv::Mat predictions; 
		parseFileAndExtractFeatures(Path::combine(test_data_path, "train.csv").c_str(), test_set, trueResponses, nFeatures, calcRadAngleHist);

		float result;
		result = bayes.predict(test_set, &predictions);	

		printPredictionStatistics(Path::combine(out_dir, "naive_bayes_train_result.txt").c_str(), trueResponses, predictions);
	}
}


void testSVM (string train_xml_path, string test_data_path, string out_dir)
{
	int nFeatures = 1050;

	int HOCS_A = 2, HOCS_B = 3, HOCS_NB = 21, HOCS_NSC = 25;

	cv::String filename = "svm_hoc_linear_svc_result.txt";
	//filename = cv::format(filename.c_str(), HOCS_A, HOCS_B, HOCS_NB, HOCS_NSC);

	cv::String filename_train = "svm_hoc_linear_svc_train_result.txt";
	//filename_train = cv::format(filename.c_str(), HOCS_A, HOCS_B, HOCS_NB, HOCS_NSC);

	cv::SVM svm;
	printf("Load SVM from %s",train_xml_path.c_str());
	svm.load(train_xml_path.c_str());
    

	//if (false)
	{ //Test results
		cv::Mat test_set;
		cv::Mat trueResponses;
		parseFileAndExtractFeatures(Path::combine(test_data_path, "test_tree_leaves.csv").c_str(), test_set, trueResponses, nFeatures, calcHoCS/*calcRadAngleHist*/);

		//ifstream ifs("D:\\University\\ML\\part_db\\scale.txt");
		//for(int i = 0; i < nFeatures; i++)
		//{
		//	Scalar mean, stddev;
		//	cv::meanStdDev(test_set.col(i), mean, stddev);

		//	float m, s;
		//	ifs >> m;
		//	ifs >> s;
		//	test_set.col(i) -= m; //mean[0];
		//	test_set.col(i) /= s; //stddev[0];
		//}
		//ifs.close();

		cv::Mat predictions = cv::Mat::zeros(test_set.rows, 1, CV_32F); //Need initialization for svm.predict
		CvMat test_set_cvmat = test_set; // convert Mat -> CvMat, data is not being copied, just reference
		CvMat predictions_cvmat = predictions; // convert Mat -> CvMat, data is not being copied, just reference

		float result;
		result = svm.predict(&test_set_cvmat, &predictions_cvmat);	

		printPredictionStatistics(Path::combine(out_dir, filename).c_str(), trueResponses, predictions, "_result");
	}
	{ // Train results
		cv::Mat test_set;
		cv::Mat trueResponses;
		parseFileAndExtractFeatures(Path::combine(test_data_path, "train_tree_leaves.csv").c_str(), test_set, trueResponses, nFeatures, calcHoCS/*calcRadAngleHist*/);

		//ifstream ifs("D:\\University\\ML\\part_db\\scale.txt");
		//for(int i = 0; i < nFeatures; i++)
		//{

		//	Scalar mean, stddev;
		//	cv::meanStdDev(test_set.col(i), mean, stddev);
		//	float m, s;
		//	ifs >> m;
		//	ifs >> s;
		//	test_set.col(i) -= m; //mean[0];
		//	test_set.col(i) /= s; //stddev[0];
		//}
		//ifs.close();

		cv::Mat predictions = cv::Mat::zeros(test_set.rows, 1, CV_32F); //Need initialization for svm.predict
		CvMat test_set_cvmat = test_set; // convert Mat -> CvMat, data is not being copied, just reference
		CvMat predictions_cvmat = predictions; // convert Mat -> CvMat, data is not being copied, just reference

		float result;
		result = svm.predict(&test_set_cvmat, &predictions_cvmat);	

		printPredictionStatistics(Path::combine(out_dir, filename_train).c_str(), trueResponses, predictions, "_train_result");
	}
}

void testKNN (string train_xml_path, string test_data_path, string out_dir)
{
	int nFeatures = 72;

	cv::Mat train_set;
	cv::Mat responses;
	parseFileAndExtractFeatures(Path::combine("D:\\University\\ML\\SVM", "train_50.csv").c_str(), train_set, responses, nFeatures, calcRadAngleHist);	

	//KNN
	cv::KNearest knn;
	knn.train(train_set, responses, Mat(), false, 5);

	cout << "### finished training ###" << endl;
	cv::imshow("qq", Mat::ones(300, 300, CV_8U));
	cv::waitKey(0);

	{ //Test results
		cv::Mat test_set;
		cv::Mat trueResponses;
		parseFileAndExtractFeatures(Path::combine(test_data_path, "test_50.csv").c_str(), test_set, trueResponses, nFeatures, calcRadAngleHist);

		cv::Mat predictions = cv::Mat::zeros(test_set.rows, 1, CV_32F); //Need initialization for svm.predict
		CvMat test_set_cvmat = test_set; // convert Mat -> CvMat, data is not being copied, just reference
		CvMat predictions_cvmat = predictions; // convert Mat -> CvMat, data is not being copied, just reference

		float result;
		result = knn.find_nearest(&test_set_cvmat, 5, &predictions_cvmat);	

		printPredictionStatistics(Path::combine(out_dir, "knn5_test_result.txt").c_str(), trueResponses, predictions);
	}
	//{ // Train results
	//	cv::Mat test_set;
	//	cv::Mat trueResponses;
	//	parseFileAndExtractFeatures(Path::combine(test_data_path, "train_50.csv").c_str(), test_set, trueResponses, nFeatures, calcRadAngleHist);

	//	cv::Mat predictions = cv::Mat::zeros(test_set.rows, 1, CV_32F); //Need initialization for svm.predict
	//	CvMat test_set_cvmat = test_set; // convert Mat -> CvMat, data is not being copied, just reference
	//	CvMat predictions_cvmat = predictions; // convert Mat -> CvMat, data is not being copied, just reference

	//	float result;
	//	result = knn.find_nearest(&test_set_cvmat, knn.get_max_k(), &predictions_cvmat);	

	//	printPredictionStatistics(Path::combine(out_dir, "nu_svm_hocs_lin_train_result.txt").c_str(), trueResponses, predictions);
	//}
}

const char * params =
	"{h | help |      | print this message   }"
	"{d | dataset | G:\\machine-learning-leaves  | path to folder with test.csv }"
	"{p | path | G:\\machine-learning-leaves  | path to folder with classifier }"
	"{o | out_dir | G:\\machine-learning-leaves  | path to result folder}"
	"{m | method  | 2  | Type of classifier: "
	" 0 - Random forest"
	" 1 - Naive Bayes"
	" 2 - SVM}"
	" 3 - KNN}";

int main(int argc, const char * argv[])
{
	CommandLineParser parser(argc, argv, params);
	string dataset = parser.get<string>("dataset");
	string outdir = parser.get<string>("out_dir");
	string path = parser.get<string>("path");
	int method = parser.get<int>("method");

	if (parser.get<bool>("help") || dataset.empty() || path.empty())
	{
		parser.printParams(); 
		return 0;
	}

	//Mat img = cv::imread("D:\\University\\ML\\datasets\\tree_leaves_test\\Juglans nigra 19.png");
	//resize(img, img, Size(), 0.25, 0.25, CV_INTER_AREA);
	//Mat dst;
	//cvtColor(img, img, COLOR_BGR2GRAY, 1);
	//threshold(img, img, 150, 255, THRESH_BINARY);
	//cv::imshow("img", img);
	//removeStem(img, dst);

	//cv::imshow("dst", dst);
	//cv::waitKey(0);


	int HOCS_A = 2, HOCS_B = 3, HOCS_NB = 21, HOCS_NSC = 25;

	cv::String filename = "svm_hoc_svc_linear.xml";
	//filename = cv::format(filename.c_str(), HOCS_A, HOCS_B, HOCS_NB, HOCS_NSC);

	if (true)
	{
		//if (argc<4)
		//{
		//	printf("Usage: testing_app.exe <path_to_folder_with_trained_classifier> <path_to_folder_with_test_csv> <method>\n");
		//	printf("where <method>:\n");
		//	printf("\t 0 for Random forest\n");
		//	printf("\t 1 for Naive Bayes\n");
		//	printf("\t 2 for SVM\n");
		//	return 1;
		//}
		switch (method)
		{
		case 0:
			testRForest(Path::combine(path, "random_forest_500_trees_d20_hra.xml"), dataset, outdir);
			break;
		case 1:
			testNaiveBayes(Path::combine(path, "naive_bayes.xml"), dataset, outdir);
			break;
		case 2:
			testSVM(Path::combine(path, filename), dataset, outdir);
			break;
		case 3:
			testKNN(Path::combine(path, "c_svm_har_lin_auto_scaled.xml"), dataset, outdir);
			break;
		}	
		return 0;

	}

	
	if (true)
	{
		string path = parser.get<string>("path");
		path = "D:\\University\\ML\\datasets\\rotate_test\\%03d.png";

		ofstream fs("D:\\University\\ML\\hists.txt");
		Mat hists(0, 21 * 25 * 2, CV_32F);
		for(int fi = 1; ; fi++)
		{
			Mat image = imread(cv::format(path.c_str(), fi));
			if(image.empty())
				break;

			
			Mat gray, bin, hist;
			cvtColor(image, gray, COLOR_BGR2GRAY);
			//threshold(gray, bin, 150, 255, THRESH_BINARY_INV);
			bin = gray;
			//cv::copyMakeBorder(bin, bin, bin.rows / 3, bin.rows / 3, bin.cols / 3, bin.cols / 3, BORDER_CONSTANT, 0);

			//resize(bin, bin, Size(), 0.5, 0.5);
			//align(bin);
			//calcRadAngleHist(bin, hist, 72);
			calcHoCS(bin, hist);

			fs << hist << endl;
			hists.push_back(hist);

			//calcShapeFeatures(bin, hist, 6);

			showHist(hist);

			cv::imshow("bin", bin);
			waitKey(0);
		}
		fs.close();

		for (int i = 0; i < 7; i++)
			for (int j = 0; j <= i; j++)
		{
			cout << i << " " << j << " " << compareHist(hists.row(i), hists.row(j), CV_COMP_INTERSECT) << endl;
		}
		return 0;
	}

	//string ds_path = parser.get<string>("dataset");
	//cv::Mat train_set;
	//cv::Mat responses;
	//parse_file(Path::combine(ds_path, "train.csv").c_str(), train_set, responses);

	//cv::Mat test_set;
	//cv::Mat trueResponses;
	//cv::Mat predictions; 
	//parse_file(Path::combine(ds_path, "test.csv").c_str(), test_set, trueResponses);


	//if (false) //Output of read responses
	//{
	//	for (int i = 0; i < responses.rows; ++i) 
	//	{ 
	//		int predicted = (int)(responses.at<float>(i)); 
	//		printf("Train set response %i\n", predicted);
	//	}
	//	for (int i = 0; i < trueResponses.rows; ++i) 
	//	{ 
	//		int predicted = (int)(trueResponses.at<float>(i)); 
	//		printf("Test set response %i\n", predicted);
	//	}
	//}

	////SVM
	//CvSVMParams params;
	//params.svm_type    = CvSVM::C_SVC;
	//params.kernel_type = CvSVM::LINEAR;
	//params.term_crit   = cvTermCriteria(CV_TERMCRIT_ITER, 100, 1e-6);

	//CvSVM a;
	//a.train_auto(train_set, responses, Mat(), Mat(), params);

	//a.predict(test_set, predictions); 
	//int n_fails = 0;
	//for (int i = 0; i < predictions.rows; ++i) 
	//{ 
	//	int predicted = (int)(predictions.at<float>(i)); 
	//	int true_value = (int)(trueResponses.at<float>(i)); 
	//	if (predicted != true_value)
	//	{
	//		printf("%i. Failed. Required %i, got %i\n", n_fails++, true_value, predicted);
	//	}

	//}

	return 0;
}